<?php

  /**
   *
   */
    class Cicp_Graph
{
    private $_id = null;
    private $_position = null;
    private $_scale = null;
    private $_nodes = array();
    private $_menus = array();
    private $_copy = null;

    function __construct($graphId = null)
    {
        if ($graphId == null) {
            $this->_id = Cicp::getInstance()->getStorageDriver()->getFreeGraphId();
        }
        else {
            $this->_id = $graphId;
        }

        // register graph at graphmanager
        Cicp::getInstance()->getGraphManager()->registerGraph($this);

        // set default values
        $this->_position = new Cicp_Vector3d_Coordinate(0, 0, 0);
        $this->_scale = new Cicp_Vector3d_Coordinate(1, 1, 1);
    }

    function __wakeup()
    {
        $this->_copy = clone $this;
    }

    function __sleep()
    {
        // delete copy of graph to save memory
        $this->_copy = null;
        return array_keys((array)$this);
    }

    function graphChanged()
    {
        // check if there's a copy to compare with
        if (!($this->_copy instanceof Cicp_Graph)) {
            return true;
        }
        // check if number of menus changed
        $menus = $this->getMenus();
        $cMenus = $this->_copy->getMenus();
        if (count($menus) != count($cMenus)) {
            return true;
        }
        // check if number of nodes changed
        $nodes = $this->getNodes();
        $cNodes = $this->_copy->getNodes();
        if (count($nodes) != count($cNodes)) {
            return true;
        }
        // check if menu ids are the same
        foreach ($menus as $index => $menu) {
            if (!isset($cMenus[$index])) {
                return true;
            }
        }
        // check if node ids are the same (none has been deleted)
        foreach ($nodes as $index => $node) {
            if (!isset($cNodes[$index])) {
                return true;
            }
        }
        // until here nothing changed, return false (=not changed)
        return false;
    }

    function getChangedNodes()
    {
        $changedNodes = array();
        /*if ($this->_copy instanceof Cicp_Graph) {
            $nodes = $this->getNodes();
            $cNodes = $this->_copy->getNodes();
            foreach ($nodes as $id => $node) {
                if (!($cNodes[$id]->equals($node))) { 
                    $changedNodes[] = $node;
                }
            }
            }*/
        $nodes = $this->getNodes();
        foreach ($nodes as $node) {
            if ($node->hasChanged()) {
                $changedNodes[] = $node;
            }
        }
        return $changedNodes;
    }

    function __toString()
    {
        return $this->toString();
    }

    function toString($create = false)
    {
        // build graph definition file (gdf) and return it as string
        $gdf = '';
        $menuStr = '';
        $nodeStr = '';
        $graphStr = '';

        if ($this->graphChanged() || $create) {
            // menu section
            foreach ($this->_menus as $menu) {
                $menuStr .= $menu;
            }
            $graphStr = $this->getCreateString();
            $nodes = $this->getNodes();

            // nodes section
            $nodeStr = "nodes\n";
        }
        else {
            $graphStr = $this->getModifyString();
            $nodes = $this->getChangedNodes();
        }

        foreach ($nodes as $node) {
            $nodeStr .= $node . "\n";
        }

        $gdf = $menuStr . $nodeStr . $graphStr;
        return $gdf;
    }

    function getCreateString()
    {
        return 'create,' . $this->getGraphString();
    }

    function getModifyString()
    {
        return 'modify,' . $this->getGraphString();
    }

    function getGraphString()
    {
        return $this->_position . ',' . $this->_scale . ','
            . $this->_id . ',' . $this->getGdfUrl();
    }

    /**
     * returns url to fetch the definition file of this graph
     */
    function getGdfUrl()
    {
        return 'http://' . $_SERVER['HTTP_HOST'] 
            . str_replace($_SERVER['DOCUMENT_ROOT'], '',
                          Cicp::getInstance()->getStorageDriver()->getGdfFilePath($this));
    }

    /**
     * adds a new node to graph and returns it
     */
    function createNode()
    {
        $node = new Cicp_Graph_Node();
        return $this->registerNode($node);
    }

    function registerNode(Cicp_Graph_Node $node)
    {
        $highestId = $this->getHighestNodeId();
        $nodeId = ++$highestId;
        $node->setId($nodeId);
        $this->_nodes[$nodeId] = $node;
        return $node;
    }

    function getHighestNodeId()
    {
        end($this->_nodes);
        return key($this->_nodes);
    }

    /**
     * removes a node from graph
     */
    function removeNode(Cicp_Graph_Node $node)
    {
        if (isset($this->_nodes[$node->getId()])) {
            unset($this->_nodes[$node->getId()]);
        }
    }

    function drawLine(Cicp_Vector3d_Coordinate $from, Cicp_Vector3d_Coordinate $to, $lineNode = null)
    {
        if (!($lineNode instanceof Cicp_Graph_Node)) {
            $lineNode = new Cicp_Graph_Node;
            $lineNode->setShape('lin');
        }

        // set vector for rest
        $node = clone $lineNode;
        $node->setPosition($from->x, $from->y, $from->z);
        $node->setDimensions($to->x, $to->y, $to->z);
        $this->registerNode($node);
        return $node;
    }

    /**
     * Sets position of graph to $x/$y/$z
     */
    function setPosition($x, $y, $z)
    {
        $this->_position->setValues($x, $y, $z);
    }

    /**
     * returns node with index $x/$y/$z
     */
    function getNodeByIndex($x, $y, $z)
    {
        foreach ($this->_nodes as $node) {
            if ($node->getNodeProperty('index')->x == $x &&
                $node->getNodeProperty('index')->y == $y &&
                $node->getNodeProperty('index')->z == $z) {
                return $node;
            }
        }
        return FALSE;
    }

    /**
     * returns node with id $nodeId
     */
    function getNodeById($nodeId)
    {
        if (isset($this->_nodes[$nodeId])) {
            return $this->_nodes[$nodeId];
        }
        else {
            throw new Exception('Node does not exist.');
        }
    }

    /**
     * returns all nodes
     */
    function getNodes()
    {
        return $this->_nodes;
    }

    /**
     * returns graph id
     */
    function getId()
    {
        return $this->_id;
    }

    /**
     * adds menu $menu to graph
     */
    function addMenu(Cicp_Graph_Menu $menu)
    {
        $this->_menus[$menu->getId()] = $menu;
    }

    /**
     * returns all registered menus in this graph
     */
    function getMenus()
    {
        return $this->_menus;
    }
}
